n---Granny Applebee's Cookie Factory---
A 4am crack                  2016-10-17
---------------------------------------

Name: Granny Applebee's Cookie Factory
Genre: educational
Year: 1993
Publisher: Micrograms
Platform: Apple //e or later (128K)
Media: single-sided 5.25-inch floppy
OS: custom
Previous cracks: none
Similar cracks:
  #797 Marty's Family Reader
  #796 Marty's Reading Workout

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  immediate disk read error

Locksmith Fast Disk Backup
  can't read track $00, sector $0C;
  copy loads title screen then breaks
  to text page with "ERROR D51"

EDD 4 bit copy (no sync, no count)
  works

Copy ][+ nibble editor
  T00,S0C exists (I searched for the
  raw nibble sequence "AA AB AE", which
  matches the second half of the track
  ("AA AA" -> $00) and the sector
  ("AB AE" -> $06 = logical sector $0C)
  in the address field

Disk Fixer
  setting "CHECKSUM ENABLED" to "NO"
  allows me to read T00,S0C

Why didn't COPYA work?
  intentionally corrupted sector on T00

Why didn't Locksmith FDB work?
  probably a run-time check to ensure
  that sector on T00 is corrupted,
  which it isn't, on my copy, because
  Locksmith Fast Disk Backup will just
  write out a standard sector of zeroes
  instead of reproducing the corruption

EDD worked. What does that tell us?
  Probably just a bad block check:
  unreadable sector = original,
  readable sector = unauthorized copy

Next steps:

  1. Use a sector editor to search for
     the error message and look nearby
     for obvious signs of sector reads
  2. If that fails, trace the boot
  3. I don't know, go feed the ducks or
     something?

                   ~

               Chapter 1
          It's Only Metadata


The disk appears to boot directly to
the program, without loading any known
operating system first. But while I was
poking around the corrupted track 0, I
noticed a normal ProDOS catalog. And in
fact, I can boot from my ProDOS hard
drive and catalog this disk!

                 --v--

]PR#7
...
]CAT,S6,D1

/BOOT6

 NAME           TYPE  BLOCKS  MODIFIED

 PRODOS          SYS       6  <NO DATE>
 TITLE           BIN      33  <NO DATE>
 LOWDOS          BIN       6  <NO DATE>
 FONT1.DHR       BIN       6  <NO DATE>
 UTL             BIN       8  <NO DATE>
 CR.COOKIE1      BIN      24  <NO DATE>
 CR.COOKIE2      BIN       9  <NO DATE>
 CR.COOKIE3      BIN       8  <NO DATE>
 CR.COOKIE4      BIN      45  <NO DATE>
 CR.COOKIE5      BIN      20  <NO DATE>
 CR.COOKIE6      BIN      13  <NO DATE>
 CR.COOKIE7      BIN      15  <NO DATE>
 CR.COOKIE8      BIN       3  <NO DATE>
 DISK            BIN       4  <NO DATE>
 FACTORY         BIN      51  <NO DATE>

BLOCKS FREE:   22     BLOCKS USED:  258

]CATALOG,S6,D1

[truncated here to show the final
 column, which is the load address of
 each file]

]CATALOG

/BOOT6

 NAME           TYPE  BLOCKS ...SUBTYPE

 PRODOS          SYS       6
 TITLE           BIN      33 ...A=$2000
 LOWDOS          BIN       6 ...A=$0800
 FONT1.DHR       BIN       6 ...A=$F000
 UTL             BIN       8 ...A=$E000
 CR.COOKIE1      BIN      24 ...A=$4000
 CR.COOKIE2      BIN       9 ...A=$4000
 CR.COOKIE3      BIN       8 ...A=$4000
 CR.COOKIE4      BIN      45 ...A=$4000
 CR.COOKIE5      BIN      20 ...A=$4000
 CR.COOKIE6      BIN      13 ...A=$4000
 CR.COOKIE7      BIN      15 ...A=$4000
 CR.COOKIE8      BIN       3 ...A=$4000
 DISK            BIN       4 ...A=$DA00
 FACTORY         BIN      51 ...A=$4000

BLOCKS FREE:   22 ...TOTAL BLOCKS:  280

                 --^--

Anyway, might prove useful, especially
being able to cross-reference sectors
to files and finding out where they're
loaded in memory. The SUBTYPE metadata
seems too non-random to be completely
unused.

Onward!

My non-working copy prints an error
message. Let's see if we can find it.
Turning to my trusty Disk Fixer sector
editor, I search for the ASCII string
"ERROR D51" and find it in T1F,S09!

Copy II Plus recognizes this disk as
ProDOS, and the "disk map" says that
T1F,S09 is part of the file "FACTORY".

                 --v--

DISK MAP                SLOT 6  DRIVE 1
/BOOT6/FACTORY

   TRACK           1               2
   0123456789ABCDEF0123456789ABCDEF012

S0 ..........................*******..
EE ..........................*******..
CD ..........................*******..
TC ..........................*******..
OB ..........................******...
RA ..........................******...
 9 ..........................******...
 8 ..........................******...
 7 ..........................******...
 6 ..........................******...
 5 ..........................******...
 4 ..........................******...
 3 ..........................******...
 2 ..........................******...
 1 .........................*******...
 F .........................*******...

   USE ARROW KEYS TO MAP OTHER FILES

                 --^--

Booting my ProDOS hard drive, I can
BLOAD that file into memory and start
tracing. According to the full CATALOG,
"FACTORY" is loaded at address $4000.

                   ~

               Chapter 1
           Enter The Factory


]PR#7
...
]PREFIX /BOOT6
]BLOAD FACTORY

NO BUFFERS AVAILABLE

Hmm, the file is too big to load from
the prompt because BASIC.SYSTEM is in
memory. However...

]BLOAD FACTORY,A$1000

works. So everything will just be off
by $3000.

]CALL -151

*1000L

1000-   4C 76 96    JMP   $9676

*6676L

; could be anything, but given the
; current program counter, I'm guessing
; this is the address $96BF, which is
; directly below
6676-   A9 BF       LDA   #$BF
6678-   8D 07 D0    STA   $D007
667B-   A9 96       LDA   #$96
667D-   8D 08 D0    STA   $D008
6680-   A9 01       LDA   #$01
6682-   8D 25 D3    STA   $D325
6685-   20 0D D3    JSR   $D30D   <-- ?
6688-   2C E8 C0    BIT   $C0E8

OK, this is not going to work. I don't
know which file is loaded at $D000, but
$D30D is not a standard ProDOS entry
point and none of the files in the disk
catalog look promising.

                   ~

               Chapter 3
         Re-enter The Factory


Attempt #2: hack the binary to break to
the monitor. (Hey, it's a non-working
copy already; it can't get any worse.)

Turning to my trusty Disk Fixer sector
editor, I press "O" for Input/Output
Control, change "DOS TYPE" to "PRODOS",
then press "D" for a directory listing
and select "FACTORY", which brings me
to the first sector on the file on
T19,S0E.

T19,S0E,$00: 4C7696 -> 4C59FF

]PR#6
...breaks (actually crashes, since I
guess a RAM bank is active and $FF59
isn't currently a valid entry point,
but I'll take what I can get...

; copy F8 ROM to language card so we
; can inspect the rest of it without
; crashing
*C089 C089 N F800<F800.FFFFM

*C08B C08B

*D30DL

D30D-   A2 08       LDX   #$08
D30F-   A9 00       LDA   #$00
D311-   9D 4B D0    STA   $D04B,X
D314-   CA          DEX
D315-   10 FA       BPL   $D311

; turn on slot 6 drive motor (hmm)
D317-   2C E9 C0    BIT   $C0E9

; don't know
D31A-   A9 01       LDA   #$01
D31C-   8D 55 D0    STA   $D055
D31F-   A9 00       LDA   #$00
D321-   8D 56 D0    STA   $D056
D324-   A9 02       LDA   #$02
D326-   8D 67 D0    STA   $D067
D329-   A9 00       LDA   #$00
D32B-   8D 68 D0    STA   $D068
D32E-   A9 D6       LDA   #$D6
D330-   A0 00       LDY   #$00
D332-   20 F9 D0    JSR   $D0F9

*D0F9L

D0F9-   8D 62 D2    STA   $D262
D0FC-   8C 61 D2    STY   $D261

; memory fiddling (not shown)
D0FF-   20 67 D2    JSR   $D267

; do something
D102-   AE 65 D0    LDX   $D065
D105-   BD 28 D9    LDA   $D928,X
D108-   20 22 D1    JSR   $D122     (1)

; increment something
D10B-   EE 62 D2    INC   $D262

; and do the same thing again, but
; differently
D10E-   AE 65 D0    LDX   $D065
D111-   BD 30 D9    LDA   $D930,X
D114-   20 22 D1    JSR   $D122     (2)


*D928.

D928- 00 04 08 0C 01 05 09 0D
D930- 02 06 0A 0E 03 07 0B 0F

OK, I'm beginning to see what's going
on here. This routine looks like it's
loading a ProDOS "block" -- two
consecutive sectors on disk, where by
"consecutive," I mean "consecutive in
the ProDOS skewing order." $D065 holds
the index into the 8-item arrays at
$D928 and $D930, which map logical to
physical sectors.

If I'm right, that means that $D122 is
the main entry point to read a sector
from disk.

*D122L

D122-   85 EC       STA   $EC

; reset data latch
D124-   AD EE C0    LDA   $C0EE
D127-   A9 03       LDA   #$03
D129-   8D 54 D0    STA   $D054
D12C-   20 41 D1    JSR   $D141

*D141L

; set up death counter
D141-   A9 00       LDA   #$00
D143-   8D 40 D1    STA   $D140
D146-   CE 40 D1    DEC   $D140
D149-   D0 03       BNE   $D14E

; if death counter hits 0, JSR(?!) here
; (more on this later)
D14B-   20 35 D2    JSR   $D235

; another death counter
D14E-   A9 00       LDA   #$00
D150-   85 FC       STA   $FC
D152-   88          DEY
D153-   D0 07       BNE   $D15C
D155-   C6 FC       DEC   $FC
D157-   D0 03       BNE   $D15C

; and again, if that death counter hits
; 0, JSR to the same place as $D14B
D159-   20 35 D2    JSR   $D235

I'm beginning to suspect that $D235
doesn't ever return, but we'll get to
that in a minute.

; find address prologue (D5 AA 96)
D15C-   AD EC C0    LDA   $C0EC
D15F-   10 FB       BPL   $D15C
D161-   C9 D5       CMP   #$D5
D163-   D0 ED       BNE   $D152
D165-   AD EC C0    LDA   $C0EC
D168-   10 FB       BPL   $D165
D16A-   C9 AA       CMP   #$AA
D16C-   D0 EE       BNE   $D15C
D16E-   AD EC C0    LDA   $C0EC
D16E-   AD EC C0    LDA   $C0EC
D171-   10 FB       BPL   $D16E
D173-   C9 96       CMP   #$96
D175-   D0 E5       BNE   $D15C

; parse address field, store in $D061+
D177-   A0 03       LDY   #$03
D179-   A9 00       LDA   #$00
D17B-   8D 60 D0    STA   $D060
D17E-   AD EC C0    LDA   $C0EC
D181-   10 FB       BPL   $D17E
D183-   2A          ROL
D184-   85 F9       STA   $F9
D186-   AD EC C0    LDA   $C0EC
D189-   10 FB       BPL   $D186
D18B-   25 F9       AND   $F9
D18D-   99 61 D0    STA   $D061,Y
D190-   4D 60 D0    EOR   $D060
D193-   88          DEY
D194-   10 E5       BPL   $D17B
D196-   A8          TAY
D197-   D0 AD       BNE   $D146
D199-   AD 63 D0    LDA   $D063
D19C-   C5 EB       CMP   $EB

; success path branches (if address
; field checksum verifies)
D19E-   F0 0B       BEQ   $D1AB

; failure path -- recalibrate the drive
; and try again to find the right track
; (not shown)
D1A0-   0A          ASL
D1A1-   85 ED       STA   $ED
D1A3-   A5 EB       LDA   $EB
D1A5-   20 9B D2    JSR   $D29B
D1A8-   4C 46 D1    JMP   $D146

; execution continues here (from $D19E)
; check if we got the sector we wanted,
; otherwise branch back and try again
D1AB-   AD 62 D0    LDA   $D062
D1AE-   C5 EC       CMP   $EC
D1B0-   D0 94       BNE   $D146
D1B2-   60          RTS

Continuing from $D12F...

*D12FL

; read data field (prologue, data, and
; epilogue -- not shown, but it sets
; the carry on failure and clears it on
; success, like DOS 3.3)
D12F-   20 B3 D1    JSR   $D1B3

; branch forward on success
D132-   90 08       BCC   $D13C

; decrement death counter and try again
D134-   CE 54 D0    DEC   $D054
D137-   D0 F3       BNE   $D12C

; once again, we end up calling $D235
; after the death counter hits 0
D139-   20 35 D2    JSR   $D235

; execution continues here (from $D132)
; this routine finishes the nibble-to-
; byte conversion of the raw nibbles
; that were read earlier in $D1B3
; (not shown)
D13C-   20 4E D2    JSR   $D24E
D13F-   60          RTS

So... we're reading sectors, more or
less the same way that DOS 3.3 reads
sectors. The strangest part is that any
fatal error ends up JSR'ing to $D235.
What's at $D235?

*D235L

; turn off slot 6 drive motor
D235-   2C E8 C0    BIT   $C0E8

; pop return address (so I was right,
; this routine never returns to the
; caller)
D238-   68          PLA
D239-   68          PLA

; jump to "fatal error" vector
D23A-   4C 06 D0    JMP   $D006

But wait! We set that vector before
calling $D30D -- all the way back at
$9676:

*9676L

; set "fatal error" vector within RWTS
9676-   A9 BF       LDA   #$BF
9678-   8D 07 D0    STA   $D007
967B-   A9 96       LDA   #$96
967D-   8D 08 D0    STA   $D008
9680-   A9 01       LDA   #$01
9682-   8D 25 D3    STA   $D325

; call RWTS
9685-   20 0D D3    JSR   $D30D

And that's the key to this protection
scheme: the "success" path routes
through the fatal error vector at $D006
and continues to the start of the game
at $96BF. If the RWTS doesn't encounter
an error, it returns to... what? Well,
the "JSR $D30D" at $9685 eventually
returns gracefully, and we continue to
the next instruction:

; execution continues here if the disk
; was read successfully -- turn off
; drive motor, display error, and hang
9688-   2C E8 C0    BIT   $C0E8
968B-   20 0F E0    JSR   $E00F
968E-   8D 0C C0    STA   $C00C
9691-   2C 51 C0    BIT   $C051
9694-   20 15 E0    JSR   $E015
9697-   A0 0A       LDY   #$0A
9699-   B9 B4 96    LDA   $96B4,Y
969C-   99 D0 07    STA   $07D0,Y
969F-   88          DEY
96A0-   10 F7       BPL   $9699
96A2-   A9 C8       LDA   #$C8
96A4-   85 45       STA   $45
96A6-   A9 5F       LDA   #$5F
96A8-   4D DA 07    EOR   $07DA
96AB-   8D DA 07    STA   $07DA
96AE-   20 18 E0    JSR   $E018
96B1-   4C A6 96    JMP   $96A6

Which is exactly the behavior I saw on
my non-working copy.

To bypass this, I should be able to
change the initial JMP from $9676 to
the success path at $96BF.

Starting over with a fresh, non-working
copy and following the "FACTORY" file
again, I arrive at an elegant one-byte
patch:

T19,S0E,$01: 76 -> BF

]PR#6
...works...

Quod erat liberandum.

---------------------------------------
A 4am crack                     No. 882
------------------EOF------------------
